Serverless FrameworkとExpressを使用してREST APIをデプロイする
Many people are using serverless architecture to deploy web applications these days because of its very fast deployment, dynamic scaling, and pay-per-invocation.
This blog is about deploying a serverless REST API using Node.js, serverless and express framework.
このブログでは、Node.js と Serverless Framework と Express をつかった、サーバーレス REST API のデプロイについてに紹介します。
Serverless Framework とは?
Serverless Framework とは Function-as-a-service の開発やデプロイを簡単に実行することができるオープンソースのツールです。
関数を作成するのに必要なコードとインフラを管理しています。
3 つの大事なコンポーネントがあります。
- 関数
プログラムのコードです。たとえばユーザー情報をデータベースに保存します。
-
イベント
AWS Lambda Function のトリガーです。たとえば AWS API Gateway の HTTP リクエストなどです。
-
リソース
インフラストラクチャコンポーネントです。たとえばユーザーデータを保存する AWS の DynamoDB です。
Express フレームワークとは?
Express はとても人気の Node.js のフレームワークです。Node.js のコードを簡単にかくことができます。
GET や POST などのルーティングを簡単に書けます
Handlebars などの View Rendering エンジンや Middleware との統合がかんたんにできます。
Rendering と configuration のためにテンプレート engine(たとえば handlebars や ejs)と env variable をセットすることができます
さいしょに
Node.js と Serverless Framework をインストールするひつようがあります。
awscli を configure することがたいせつです
エクスプレスフレームワークのきそちしきことがたいせつです
API について
- この REST API の Backend はサーバーレスです
- ユーザー ID を保存するために CRUD(Create,Read,Update,Delete)のオペレーションをします
- Lambda と API Gateway と DynamoDB は Serverless Framework によってデプロイされます
- API のコードはGitHubにあります
Express の作成
プロジェクトのために Directory をつくって、Express-Generator を使用して Application の Skeleton をつくります
mkdir serverless_blog;cd serverless_blog npx express-generator --view=hbs npm install ; npm i serverless-http aws-sdk
app.js
のコード
const serverless = require('serverless-http'); app.use('/', indexRouter); app.use('/users',usersRouter); //module.exports = app; module.exports.handler = serverless(app);
- root directory のなかに
users.js
ファイルを作成
const AWS = require('aws-sdk'); const dynamoDb = new AWS.DynamoDB.DocumentClient(); var express = require('express'); var router = express.Router(); router.get('/', function(req, res, next) { res.status(200).json({Welcome_msg:'Use create,read,update,delete routes to this Express API '}); }); router.post('/create', function(req, res, next) { const { id, firstName } = req.body; if (typeof id !== 'string') { res.status(400).json({ error: '"id" must be a string' }); } else if (typeof firstName !== 'string') { res.status(400).json({ error: '"firstName" must be a string' }); } const params = { TableName: 'Notes', Item: { "id": id, "firstName": firstName, }, ReturnValues: 'ALL_OLD' }; dynamoDb.put(params, (error,data) => { if (error) { console.log(error); res.status(400).json({ error:error }); }else if(Object.keys(data).length === 0 && data.constructor === Object){ res.status(200).json({Item:{id,firstName}}) } else{ res.status(400).json({Item:'Item already exist'}); } }); }) router.post("/readUser", async function (req, res) { id=req.body.id res.redirect('/dev/users/readUser/' + id) }) router.get("/read/:id", async function (req, res) { const params = { TableName: 'Notes', Key: { "id": req.params.id, } }; try { dynamoDb.get(params, function (err, data) { if(err){ res.status(400).json({ error: 'Could not read user' }); }else if (Object.keys(data).length === 0 && data.constructor === Object) { res.status(400).json({ error: 'Item does not exist' }); } else { res.status(200).json(data); } }) } catch (error) { console.log(error); res.status(500).json({ error: "Could not retreive user" }); } }); router.put('/update/:id', function(req, res, next) { const { id, firstName } = req.body; if (typeof id !== 'string') { res.status(400).json({ error: '"id" must be a string' }); } else if (typeof firstName !== 'string') { res.status(400).json({ error: '"firstName" must be a string' }); } const params = { TableName: 'Notes', Key: { "id": req.params.id }, UpdateExpression: "set firstName = :y", ConditionExpression: "attribute_exists(id)", ExpressionAttributeValues:{ ":y":firstName }, ReturnValues:"UPDATED_NEW" }; dynamoDb.update(params, (error,data) => { if (error) { console.log(error); res.status(400).json({ error:"Item does not exist for updation" }); } else { res.status(200).json(data); } }); }) router.delete('/delete/:id',function(req, res, next) { const params = { TableName: 'Notes', Key: { "id": req.params.id, }, ConditionExpression: "attribute_exists(id)", ReturnValues: 'ALL_OLD' }; dynamoDb.delete(params, function (error,data) { if (error) { console.log(error); res.status(400).json({ error:"Item does not exist for deletion" }); } else { res.status(200).json(data); } }); }) module.exports = router;
プロジェクトの root でserverless.yml
をつくります。
serverless.yml
のコード
service: node-serverless-api # app and org for use with dashboard.serverless.com app: node-serverless-api org: jatinjmehrotra # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details frameworkVersion: "2" provider: name: aws runtime: nodejs12.x lambdaHashingVersion: 20201221 region: ap-southeast-1 iam: role: statements: - Effect: Allow Action: - dynamodb:DescribeTable - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: arn:aws:dynamodb:ap-southeast-1:*:* resources: Resources: NotesTable: Type: "AWS::DynamoDB::Table" Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: "Notes" functions: hello: handler: app.handler events: - http: path: /user method: ANY - http: path: /user/{proxy+} method: ANY
serverless.yml
ファイルのなかに、Lambda で DynamoDB へアクセスするのための IAM Role のアクセス許可を作っています。
リソースブロックのなかには、NotesTable
というなまえの DynamoDB テーブルをつくっています。
そしてイベントブロックで、Lambda をトリガーするために Path をつくっています。
NOTE: DynamoDB テーブルの Attribute Name に Reserved Name をつかわないでください。Expression Attribute Name をつかってください
Express API のデプロイとテスト
Lambda のデプロイのためにこのコマンドをじっこうしてください。
sls deploy --aws-profile personal
NOTE: コマンドをじっこうしたあとで「profile is not configured error」というエラーメッセージがでたら、このstackoverflow の postを check をしてください
コマンドのじっこうに成功したら API の Endpoint が得られます。
API のテストのためには Curl コマンドをつかってください。もしくは、Postman をつかうこともできます。
- POST
curl -H "Content-Type: application/json" -X POST <https://3469t9kwr1.execute-api.ap-southeast-1.amazonaws.com/dev/user/create> -d '{"id":"1","firstName": "testuser"}'
- GET
curl -H "Content-Type: application/json" -X GET <https://3469t9kwr1.execute-api.ap-southeast-1.amazonaws.com/dev/user/read/1
- PUT
curl -H "Content-Type: application/json" -X PUT <https://3469t9kwr1.execute-api.ap-southeast-1.amazonaws.com/dev/user/update/1> -d '{"id":"1","firstName": "test123"}'
- DELETE
curl -H "Content-Type: application/json" -X DELETE <https://3469t9kwr1.execute-api.ap-southeast-1.amazonaws.com/dev/user/delete/1>
つくった Infrastructure は削除しましょう。
sls remove--aws-profile personal
さいごに
Serverless Framework を使用すれば REST API をデプロイするのがとてもかんたんになります。
Serverless Framework はフレキシブルでいろいろなクラウドプロバイダーと統合できます。
Serverless Framework のほかの例はこのリンクをみてください
それでは、Happy Learning!